/*******************************************************************************
 * Copyright (c) 2016 fortiss GmbH and Herwig Eichler, www.conmeleon.org
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Alois Zoitl and Herwig Eichler
 *******************************************************************************/

#include "core/fmi/processinterface.h"

const char *const CConmeleonC1ProcessInterface::scmUnknownChannel = "Channel number not existing";
const char *const CConmeleonC1ProcessInterface::scmChannelNotSupported = "Channel type not supported by hardware";
const char *const CConmeleonC1ProcessInterface::scmChannelInUse = "Channel already in use by other FB";
const char *const CConmeleonC1ProcessInterface::scmInitDeinitOK = "OK";
const char *const CConmeleonC1ProcessInterface::scmNotInitialised = "Not initialized";
const char *const CConmeleonC1ProcessInterface::scmOK = "OK";

CONMELEON::CGpioPin CConmeleonC1ProcessInterface::smDigitalInputs[] = {
    {20, CONMELEON::input}, {21, CONMELEON::input}, {22, CONMELEON::input}, {23, CONMELEON::input}};

CONMELEON::CGpioPin CConmeleonC1ProcessInterface::smDigitalOutputs[] = {
    {24, CONMELEON::output}, {25, CONMELEON::output}, {26, CONMELEON::output}, {27, CONMELEON::output}};

CONMELEON::CAds1018 CConmeleonC1ProcessInterface::smADC("/dev/spidev0.1", 1000000, CONMELEON::SPIMODE1);

CConmeleonC1ProcessInterface::EIOState CConmeleonC1ProcessInterface::smAIUsage[5] = {
    CConmeleonC1ProcessInterface::enFree, CConmeleonC1ProcessInterface::enFree, CConmeleonC1ProcessInterface::enFree,
    CConmeleonC1ProcessInterface::enFree, CConmeleonC1ProcessInterface::enFree};

CConmeleonC1ProcessInterface::EIOState CConmeleonC1ProcessInterface::smDIUsage[4] = {
    CConmeleonC1ProcessInterface::enFree, CConmeleonC1ProcessInterface::enFree, CConmeleonC1ProcessInterface::enFree,
    CConmeleonC1ProcessInterface::enFree};

CConmeleonC1ProcessInterface::EIOState CConmeleonC1ProcessInterface::smDOUsage[4] = {
    CConmeleonC1ProcessInterface::enFree, CConmeleonC1ProcessInterface::enFree, CConmeleonC1ProcessInterface::enFree,
    CConmeleonC1ProcessInterface::enFree};

CConmeleonC1ProcessInterface::CConmeleonC1ProcessInterface(forte::CFBContainer &paContainer,
                                                           const SFBInterfaceSpec &paInterfaceSpec,
                                                           const forte::StringId paInstanceNameId) :
    CProcessInterfaceBase(paContainer, paInterfaceSpec, paInstanceNameId),
    mChannelNr(-1),
    mCallingFB(enUnsupported) {

  // initialize the ADC settings
  smADC.setPGA(CONMELEON::FS4096); // maximum input voltage of ADC is 3.3V so we use 4.096V as full scale setting
  smADC.setVoltageDivider(3.0); // CONMELEON C1 input voltage divider has value of 3.0:  10.0V to 3.3V
  smADC.setConversionMode(CONMELEON::SingleShot); // set to single shot conversion mode
}

CConmeleonC1ProcessInterface::~CConmeleonC1ProcessInterface() {
}

bool CConmeleonC1ProcessInterface::initialise(bool paIsInput, CEventChainExecutionThread *const paECET) {

  CIEC_INT param;
  param.fromString(PARAMS().getStorage().c_str());
  mChannelNr = param;
  setCallingFBType(mInputOrOutput);

  if (!checkChannelBoundaries()) {
    STATUS() = scmUnknownChannel;
    return false;
  }

  if (checkCallingFBTypeIsIW()) {
    // we need to handle an analog input
    if (smAIUsage[mChannelNr - 1] == enFree) {
      smAIUsage[mChannelNr - 1] = enUsed;
    } else {
      STATUS() = scmChannelInUse;
      return false;
    }
  } else if (checkCallingFBTypeIsIX()) {
    // we need to handle an digital input
    if (smDIUsage[mChannelNr - 1] == enFree) {
      smDIUsage[mChannelNr - 1] = enUsed;
      // conmeleon c1 digital inputs have inverted logic level
      smDigitalInputs[mChannelNr - 1].setInverted(true);
    } else {
      STATUS() = scmChannelInUse;
      return false;
    }
  } else if (checkCallingFBTypeIsQX()) {
    // we need to handle an digital output
    if (smDOUsage[mChannelNr - 1] == enFree) {
      smDOUsage[mChannelNr - 1] = enUsed;
    } else {
      STATUS() = scmChannelInUse;
      return false;
    }
  } else {
    STATUS() = scmChannelNotSupported;
    return false;
  }
  STATUS() = scmInitDeinitOK;
  return true;
}

bool CConmeleonC1ProcessInterface::deinitialise() {

  if (checkChannelBoundaries()) {

    if (checkCallingFBTypeIsIW()) {
      smAIUsage[mChannelNr - 1] = enFree;
    } else if (checkCallingFBTypeIsIX()) {
      smDIUsage[mChannelNr - 1] = enFree;
    } else if (checkCallingFBTypeIsQX()) {
      smDOUsage[mChannelNr - 1] = enFree;
    }
  }
  mChannelNr = -1;
  mCallingFB = enUnsupported;
  STATUS() = scmInitDeinitOK;
  return true;
}

bool CConmeleonC1ProcessInterface::readPin() {

  if (!checkChannelBoundaries()) {
    STATUS() = scmUnknownChannel;
    return false;
  }
  if (checkCallingFBTypeIsIX()) {
    IN_X() = smDigitalInputs[mChannelNr - 1].read();
    STATUS() = scmOK;
    return true;
  } else {
    STATUS() = scmChannelNotSupported;
    return false;
  }
}

bool CConmeleonC1ProcessInterface::writePin() {

  if (!checkChannelBoundaries()) {
    STATUS() = scmUnknownChannel;
    return false;
  }
  if (checkCallingFBTypeIsQX()) {
    smDigitalOutputs[mChannelNr - 1].write(OUT_X());
    STATUS() = scmOK;
    return true;
  } else {
    STATUS() = scmChannelNotSupported;
    return false;
  }
}

bool CConmeleonC1ProcessInterface::readWord() {

  if (!checkChannelBoundaries()) {
    STATUS() = scmUnknownChannel;
    return false;
  }

  if (checkCallingFBTypeIsIW()) {
    if (mChannelNr <= 4) {
      // read voltage input
      IN_W() = static_cast<TForteWord>(smADC.readVoltage(mChannelNr - 1));
    }
    if (mChannelNr == 5) {
      // read temperature input
      IN_W() = static_cast<TForteWord>(smADC.readTemperature());
    }
    STATUS() = scmOK;
    return true;
  } else {
    STATUS() = scmChannelNotSupported;
    return false;
  }
}

void CConmeleonC1ProcessInterface::setCallingFBType(bool paIsInput) {

  mCallingFB = enUnsupported;

  if (paIsInput) {
    // the only way is to check the interface of the FB
    if (getDO(2)->getDataTypeID() == CIEC_ANY::e_WORD) {
      // IW FB called
      mCallingFB = enIW;
    } else if (getDO(2)->getDataTypeID() == CIEC_ANY::e_BOOL) {
      // IX FB called
      mCallingFB = enIX;
    }
  } else {
    if (getDI(2)->getDataTypeID() == CIEC_ANY::e_BOOL) {
      // QX FB called
      mCallingFB = enQX;
    }
  }
}

bool CConmeleonC1ProcessInterface::checkChannelBoundaries() const {
  // channel number should be between 1..4 for DI and DO and 1..5 for AI
  if ((mChannelNr > 0) && ((checkCallingFBTypeIsIW() && (mChannelNr <= 5)) || (mChannelNr <= 4))) {
    return true;
  }
  return false;
}
